<?php        
    class LoginControl extends Control {
        protected static $_data = array(
            "sessionid" => null,    # Session id
            "userid"    => null,    # Käyttäjän id
            "userdata"  => null     # Käyttäjän tiedot (UserData objekti)
        );
        
        protected static $_callable = array(
            "doLogin",
            "doLogout",
            "refreshLogin"
        );
        
        protected static $_noLogin = array(
            "doLogin"
        );
        
        /**
         * Funktio tarkastaa sessionid:n avulla onko käyttäjä kirjautunut palveluun.
         *
         * Mikäli kirjautuminen löytyy asetataan myös userid ja userdata.
         *
         * @return  bool
         */
        private static function _checkLogin() {
            $result = false;
            
            # Passivoi vanhat kirjautumiset
            DB::query("
                UPDATE login
                INNER JOIN user ON user.id = login.userid
                SET login.endtime = NOW()
                WHERE TIMESTAMPDIFF(MINUTE,login.accesstime,NOW()) > user.timeout_min
                  AND login.endtime IS NULL
            ");
            
            # Vie vanhat kirjautumiset login_history tauluun
            DB::query("
                INSERT INTO login_history (userid,sessionid,ip,accesstime,endtime,rowstamp)
                SELECT userid,sessionid,ip,accesstime,endtime,rowstamp
                FROM login
                WHERE endtime IS NOT NULL
            ");
            
            # Poista vanhat kirjautumiset login taulusta
            DB::query("
                DELETE FROM login
                WHERE endtime IS NOT NULL
            ");
                        
            # Tarkasta onko kirjauduttu
            if (self::get("sessionid") !== null) {                
                $query = DB::preparedQuery("
                    SELECT id,userid
                    FROM login
                    WHERE sessionid=:sessionid
                      AND endtime IS NULL
                    ",array(
                        "sessionid" => self::get("sessionid")
                    )
                );
                if (DB::numRows($query) == 1) {                    
                    $arr = DB::fetchAssoc($query);                    
                    $affrows = DB::update("login",array(
                            "ip"            => ip2long(self::_getIP()),
                            "accesstime"    => date("Y-m-d H:i:s")                            
                        ),$arr["id"]
                    );
                    if (DB::error() != 0) {
                        throw new DBException(
                            "Failed to update the session",
                            20,
                            __CLASS__,
                            "Failed to update the session"
                        );
                    }
                    
                    Model::$currentUserId = $arr["userid"];
                    self::set("userid",$arr["userid"]);
                    self::set("userdata",new UserModel($arr["userid"]));                                                            
                    
                    $timezone = new TimeZoneModel(self::get("userdata")->timezoneid);
                    Model::$currentTimeZone = $timezone->timezone;
                    
                    $result = true;
                } else {
                    self::set("sessionid",null);
                }
                DB::freeResult($query);
            }
            
            return $result;
        }
        
        /**
         * Luo käyttäjälle session login tauluun ja palauttaan sessionid:n.
         *
         * @return  string
         */
        private static function _createSession($userid) {
            DB::begin();
            
            try {
                $insertid = DB::insert("login",array(
                    "accesstime"    => date("Y-m-d H:i:s"),
                    "userid"        => $userid,
                    "ip"            => ip2long(self::_getIP())
                ));
                
                # Insert epäonnistui
                if (!$insertid) {
                    throw new DBException(
                        "Failed to create a session",
                        10,
                        __CLASS__,
                        "Failed to create a session"
                    );
                }
                
                # Muodosta sessioid
                $sessionid = md5(str_repeat("0",(10-strlen($insertid))))."-".
                    date("YmdHis")."-".
                    bin2hex(openssl_random_pseudo_bytes(16));
                
                $affrows = DB::update("login",array("sessionid" => $sessionid),$insertid);
                
                # Päivitys epäonnistui
                if ($affrows != 1) {
                    throw new DBException(
                        "Failed to set the sessionid",
                        20,
                        __CLASS__,
                        "Failed to set the sessionid"
                    );    
                }
            } catch (Exception $e) {
                DB::rollback();
                throw $e;
            }
            
            DB::commit();
            
            return $sessionid;
        }
        
        /**
         * Palauttaa kutsuvan asiakkaan IP:n.
         *
         * @return  string
         */
        private static function _getIP() {
            if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
                return $_SERVER["HTTP_X_FORWARDED_FOR"];
            }

            if (isset($_SERVER["HTTP_CLIENT_IP"])) {
                return $_SERVER["HTTP_CLIENT_IP"];
            }

            if (isset($_SERVER["REMOTE_ADDR"])) {
                return $_SERVER["REMOTE_ADDR"];
            }
            
            return "";
        }
    
        /**
         * Funktio kertoo onko käyttäjällä oikeutta kutsuun.
         *
         * @param   string  $class      Kutsuttava luokka
         * @param   string  $function   Kutsuttava funktio
         * @return  bool
         */
        public static function checkAccess($class,$function) {
            $access = false;
            
            if (method_exists($class,$function)) {
                # Tarkastetaan onko funktio yläpäätään API:n kautta kutsuttava
                $isCallable = "isCallable";
                $loginRequired = "loginRequired";
                
                if (in_array($function,array($isCallable,$loginRequired))) {
                    throw new APIException("Access denied",40,__CLASS__);
                }
                
                if (method_exists($class,$isCallable)) {
                    $access = $class::$isCallable($function);
                }
                
                # Tarkasta kirjautuminen
                if ($access) {                    
                    $needLogin = true;                    
                    if (method_exists($class,$loginRequired)) {
                        $needLogin = $class::$loginRequired($function);
                    }
                    
                    # Ei kirjautumista
                    $checkLogin = self::_checkLogin();
                    if ($needLogin && !$checkLogin) {
                        throw new APIException("No session",60,__CLASS__);
                    }
                }
            }
            
            # Ei oikeutta
            if (!$access) {
                throw new APIException("Access denied",40,__CLASS__);
            }
            
            return true;
        }
        
        /**
         * Kirjaa käyttäjä sisälle.
         *
         * Funktio palauttaa käyttäjän sessionid:n tai jos kirjautuminen epäonnistuu
         * niin palautetaan boolean false.
         *
         * @param   array   data
         * @return  mixed
         */
        public static function doLogin($data) {
            $result = false;
            
            # Sposti tai salasana puuttuu
            if (!isset($data["email"]) || !isset($data["password"])) {
                throw new APIException("Login failed",50,__CLASS__);
            }
            
            $query = DB::preparedQuery("SELECT id,password FROM user WHERE email=:email",array(
                "email" => $data["email"]
            ));
            if (DB::numRows($query) == 1) {
                $arr = DB::fetchAssoc($query);
                # Salasana ok
                if (password_verify($data["password"],$arr["password"])) {                    
                    $result = self::_createSession($arr["id"]);
                }
            }
            DB::freeResult($query);
            
            # Kirjautuminen epäonnistui
            if (!$result) {
                throw new APIException("Login failed",50,__CLASS__);
            }
            
            return $result;
        }
        
        /**
         * Funktio kirjaa käyttäjän ulos palvelusta.
         *
         * @return  bool
         */
        public static function doLogout() {
            $result = false;
            
            if (self::get("sessionid") !== null) {
                $affrows = DB::update("login",array(
                        "ip"        => ip2long(self::_getIP()),
                        "endtime"   => date("Y-m-d H:i:s")
                    ),array(
                        "sessionid" => self::get("sessionid")
                    )
                );
                if (DB::error() != 0) {
                    throw new DBException(
                        "Failed to end the session",
                        20,
                        __CLASS__,
                        "Failed to end the session"
                    );
                }
                
                self::set("sessionid",null);
                $result = true;
                
                # Passivoi vanhat kirjautumiset
                self::_checkLogin();
            }
            
            return $result;
        }
        
        public static function refreshLogin() {
            return true;
        }
    }